﻿// <copyright file="MultiGeometry.cs" company="Signal Processing Lab, Wuhan University">
// Copyright (c) 2009-2010 All Right Reserved
// </copyright>
// <author>Xinping Deng</author>
// <email>dentoby@gmail.com</email>
// <date>2010-4</date>
// <summary>This is part of Image.NET project.</summary>

namespace SPLWHU.Core.Vectors
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    /// <summary>
    /// The <see cref="Geometry"/> collection.
    /// </summary>
    /// <typeparam name="T">The geometry type.</typeparam>
    public class MultiGeometry<T> : Geometry, IEnumerable<T> where T : Geometry
    {
        /// <summary>
        /// The collection of geometries.
        /// </summary>
        private List<T> geometries;

        /// <summary>
        /// Initializes a new instance of the <see cref="MultiGeometry&lt;T&gt;"/> class.
        /// </summary>
        public MultiGeometry()
        {
            this.geometries = new List<T>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MultiGeometry&lt;T&gt;"/> class by specifying a list of geometries.
        /// </summary>
        /// <param name="geometries">The geometries to be added to the collection.</param>
        public MultiGeometry(List<T> geometries)
        {
            this.geometries = geometries;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MultiGeometry&lt;T&gt;"/> class by specifying an array of geometries.
        /// </summary>
        /// <param name="geometries">The geometries to be added to the collection.</param>
        public MultiGeometry(params T[] geometries)
            : this()
        {
            foreach (T g in geometries)
            {
                this.geometries.Add(g);
            }
        }

        /// <summary>
        /// Gets the geometry count in this geometry.
        /// </summary>
        /// <value>The geometry count.</value>
        /// <remarks>
        /// A <see cref="Point2d"/> or <see cref="LineString"/> instance return zero.
        /// </remarks>
        public override int GeometryCount
        {
            get { return this.geometries.Count; }
        }

        /// <summary>
        /// Gets the centroid of this instance. Normally, returns the centroid of the bounding box.
        /// </summary>
        /// <value>The centroid of this instance.</value>
        public override Point2d Centroid
        {
            get 
            {
                // Gets the bounding box.
                BoundingBox bbox = this.GetExtents();
                
                // The bounding box is empty.
                if (bbox == null)
                {
                    return null;
                }

                // Returns the centroid of the bounding box.
                return bbox.Centroid; 
            }
        }

        /// <summary>
        /// Gets or sets the geometry at the specified index.
        /// </summary>
        /// <value>The geometry at the specified position.</value>
        public T this[int index]
        {
            get
            {
                try
                {
                    return this.geometries[index];
                }
                catch (Exception ex)
                {
                    throw new VectorException(ex.Message);
                }
            }

            set
            {
                try
                {
                    this.geometries[index] = value;
                }
                catch(Exception ex)
                {
                    throw new VectorException(ex.Message);
                }
            }
        }

        /// <summary>
        /// Gets the geometry in this instance at the specified index.
        /// </summary>
        /// <param name="index">The geometry index.</param>
        /// <returns>The geometry at the specified position.</returns>
        public override Geometry GetGeometry(int index)
        {
            return this[index];
        }

        /// <summary>
        /// Gets the bounding box of this geometry.
        /// </summary>
        /// <returns>The bounding box of this geometry.</returns>
        public override BoundingBox GetExtents()
        {
            // If the geometry count is 0, return a null value.
            int cnt = this.geometries.Count;
            if (cnt < 0)
            {
                return null;
            }

            // Gets the union of bounding boxes of all the geometries.
            BoundingBox bbox = this.geometries[0].GetExtents();
            for (int i = 1; i < cnt; i++)
            {
                bbox = bbox.Joint(this.geometries[i].GetExtents());
            }

            // Returns.
            return bbox;
        }

        /// <summary>
        /// Adds a geometry to the collection.
        /// </summary>
        /// <param name="geometry">The geometry to be added.</param>
        public void Add(T geometry)
        {
            this.geometries.Add(geometry);
        }

        /// <summary>
        /// Adds a list of geometries to the collection.
        /// </summary>
        /// <param name="geometries">The geometries added.</param>
        public void Add(List<T> geometries)
        {
            foreach (T g in geometries)
            {
                this.geometries.Add(g);
            }
        }

        /// <summary>
        /// Adds an array of geometries to the collection.
        /// </summary>
        /// <param name="geometries">The geometries added.</param>
        public void Add(params T[] geometries)
        {
            foreach (T g in geometries)
            {
                this.geometries.Add(g);
            }
        }

        /// <summary>
        /// Inserts a geometry at the specified index.
        /// </summary>
        /// <param name="index">The index to insert the geometry.</param>
        /// <param name="geometry">The geometry inserted.</param>
        public void Insert(int index, T geometry)
        {
            this.geometries.Insert(index, geometry);
        }

        /// <summary>
        /// Removes the specified geometry from the collection.
        /// </summary>
        /// <param name="geometry">The geometry removed.</param>
        public void Remove(T geometry)
        {
            this.geometries.Remove(geometry);
        }

        /// <summary>
        /// Removes the geometry at the specified index.
        /// </summary>
        /// <param name="index">The index of the geometry removed.</param>
        public void RemoveAt(int index)
        {
            this.geometries.RemoveAt(index);
        }

        /// <summary>
        /// Clears the geometries in the collection.
        /// </summary>
        public void Clear()
        {
            this.geometries.Clear();
        }

        /// <summary>
        /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
        /// <returns>
        /// <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="T:System.NullReferenceException">
        /// <paramref name="obj"/> the parameter is null。</exception>
        public override bool Equals(object obj)
        {
            // The compared object must be a geometry collection.
            MultiGeometry<T> geos = obj as MultiGeometry<T>;
            if (geos == null)
            {
                return false;
            }

            // The count of geometries must be equal.
            int cnt = geos.GeometryCount;
            if (cnt != this.GeometryCount)
            {
                return false;
            }

            // Each geometry must be equal.
            for (int i = 0; i < cnt; i++)
            {
                if (!geos[i].Equals(this[i]))
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override int GetHashCode()
        {
            int hash = base.GetHashCode();

            foreach (T g in this.geometries)
            {
                hash = hash ^ g.GetHashCode();
            }

            return hash;
        }

        /// <summary>
        /// Gets the enumerator of geometries in this collection.
        /// </summary>
        /// <returns>
        /// The enumerator <see cref="T:System.Collections.Generic.IEnumerator`1"/> to visit this collection.
        /// </returns>
        public IEnumerator<T> GetEnumerator()
        {
            return this.geometries.GetEnumerator();
        }

        /// <summary>
        /// Gets the enumerator of geometries in this collection.
        /// </summary>
        /// <returns>
        /// The enumerator <see cref="T:System.Collections.IEnumerator"/> to visit this collection.
        /// </returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }
    }
}
